import GomokuCommon
import UIKit

// 游戏
class Game {
    // 棋子类型
    public enum Piece {
        case NOTHING    // 什么也没有
        case BLACK      // 黑棋
        case WHITE      // 白棋
    }

    // 状态
    public enum Status {
        case END            // 结束
        case PLAYING        // 自己下棋中
        case RIVAL_PLAYING  // 对手下棋中
    }

    private let mDrawer: CGContext      // 绘图者
    private let mGameBoard: UIView      // 游戏区域
    private var mIsBlack: Bool!         // 黑棋则为 true，白棋则为 false
    private var mLastX: Int             // 上一次放置棋子的位置的 X 坐标
    private var mLastY: Int             // 上一次放置棋子的位置的 Y 坐标
    private let mPieceRadius: Int       // 棋子半径
    private var mPieces: [[Piece]]!     // 所有棋子
    private var mThread: Thread?        // 接收数据的线程
    private var mRivalName: String!     // 对手的名字
    private var mSocket: Int32!         // 套接字
    private var mStatus: Status         // 状态
    private var mUsername: String!      // 用户名

    /**
     * 构造方法。
     * @param drawer    绘图者
     * @param gameBoard 游戏区域
     */
    public init(_ drawer: CGContext, gameBoard: UIView) {
        // 初始化成员
        mDrawer = drawer
        mGameBoard = gameBoard
        mLastX = 0
        mLastY = 0
        mPieceRadius = Const.GRID_SIZE * 4 / 10
        mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
        mStatus = Status.END
        mUsername = ""
        
        // 重绘游戏区域
        self.drawGameBoard()
        
        // 连接服务器
        var addr = sockaddr_in()
        memset(&addr, 0, MemoryLayout<sockaddr_in>.size)
        addr.sin_len = __uint8_t(MemoryLayout<sockaddr_in>.size)
        addr.sin_family = sa_family_t(AF_INET)
        addr.sin_port = _OSSwapInt16(GomokuProtocol.SERVER_PORT)
        addr.sin_addr.s_addr = inet_addr(GomokuProtocol.SERVER_IP)
        var serverAddr = sockaddr()
        memcpy(&serverAddr, &addr, MemoryLayout<sockaddr_in>.size)
        let result = connect(mSocket, &serverAddr, socklen_t(MemoryLayout<sockaddr_in>.size)) == 0

        // 开启接收数据的线程
        if result {
            mThread = Thread(target: self, selector: #selector(receiveData), object: nil)
            mThread?.start()
        }

        // 发送通知
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: Const.UM_CONNECTED, object: result)
        }
    }

    /**
     * 接受或拒绝挑战。
     * @param rivalName 对手的名字
     * @param accept    接受挑战则为 true，否则为 false
     */
    public func acceptInvite(_ rivalName: String, accept: Bool) {
        // 如果接受则开始游戏
        if accept {
            mRivalName = rivalName
            self.start(false)
        }

        // 发送结果
        let result = InviteResult(accept, rivalName: mUsername, username: rivalName)
        self.sendData(result)
    }

    /**
     * 检查横向。
     * @return 胜利则返回 true，否则返回 false
     */
    private func checkHorizontal() -> Bool {
        let checkPiece = mIsBlack ? Piece.BLACK: Piece.WHITE   // 要检查的棋子
        var count = 0                                           // 连续的棋子的数量

        // 从左到右检查一行
        for i in 0 ..< Const.GAME_BOARD_POINT_COUNT_H {
            // 查找棋子
            if mPieces[mLastY][i] == checkPiece {
                count += 1
            } else {
                count = 0
            }

            // 如果5个棋子连在一起就胜利
            if count == Const.WIN_NUMBER {
                return true
            }
        }

        return false
    }

    /**
     * 检查斜向（从左上到右下）。
     * @return 胜利则返回 true，否则返回 false
     */
    private func checkOblique1() -> Bool {
        let checkPiece = mIsBlack ? Piece.BLACK: Piece.WHITE   // 要检查的棋子
        var count = 0                                           // 连续的棋子的数量
        let min = mLastX >= mLastY ? mLastY: mLastX            // 范围坐标，用以确定左边界和上边界
        let left = mLastX - min                                 // 左边界
        let top = mLastY - min                                  // 上边界

        // 从左上到右下检查一斜行
        var i = left, j = top
        while i < Const.GAME_BOARD_POINT_COUNT_H && j < Const.GAME_BOARD_POINT_COUNT_V {
            // 查找棋子
            if mPieces[j][i] == checkPiece {
                count += 1
            } else {
                count = 0
            }

            // 如果5个棋子连在一起就胜利
            if count == Const.WIN_NUMBER {
                return true
            }

            i += 1
            j += 1
        }

        return false
    }

    /**
     * 检查斜向（从左下到右上）。
     * @return 胜利则返回 true，否则返回 false
     */
    private func checkOblique2() -> Bool {
        let checkPiece = mIsBlack ? Piece.BLACK: Piece.WHITE   // 要检查的棋子
        var count = 0                                           // 连续的棋子的数量
        var left = 0                                            // 左边界
        var bottom = 0                                          // 下边界

        // 计算左边界和下边界
        if mLastX + mLastY < Const.GAME_BOARD_POINT_COUNT_H {
            bottom = mLastX + mLastY
        } else {
            left = (mLastX + mLastY) - Const.GAME_BOARD_POINT_COUNT_H + 1
            bottom = Const.GAME_BOARD_POINT_COUNT_V - 1
        }

        // 从左下到右上检查一斜行
        var i = left, j = bottom
        while i < Const.GAME_BOARD_POINT_COUNT_H && j >= 0 {
            // 查找棋子
            if mPieces[j][i] == checkPiece {
                count += 1
            } else {
                count = 0
            }

            // 如果5个棋子连在一起就胜利
            if count == Const.WIN_NUMBER {
                return true
            }

            i += 1
            j -= 1
        }

        return false
    }

    /**
     * 检查纵向。
     * @return 胜利则返回 true，否则返回 false
     */
    private func checkVertical() -> Bool {
        let checkPiece = mIsBlack ? Piece.BLACK: Piece.WHITE   // 要检查的棋子
        var count = 0                                           // 连续的棋子的数量

        // 从上到下检查一列
        for j in 0 ..< Const.GAME_BOARD_POINT_COUNT_V {
            // 查找棋子
            if mPieces[j][mLastX] == checkPiece {
                count += 1
            } else {
                count = 0
            }

            // 如果5个棋子连在一起就胜利
            if count == Const.WIN_NUMBER {
                return true
            }
        }

        return false
    }
    
    /**
     * 关闭游戏。
     */
    public func close() {
        // 发送退出游戏请求
        let quitGame = QuitGame(mUsername)
        self.sendData(quitGame)
        
        // 延迟一下，防止数据还没发送完成就关闭网络
        Thread.sleep(forTimeInterval: 0.01)

        // 停止线程
        mThread?.cancel()
        
        // 关闭网络
        Darwin.close(mSocket)
    }
    
    /**
     * 认输。
     */
    public func defeat() {
        // 发送游戏结束数据
        let gameOver = GameOver(.SELF_ADMIT, rivalName: mRivalName)
        self.sendData(gameOver)

        // 游戏结束
        self.gameOver(.SELF_ADMIT)
    }
    
    /**
     * 画游戏区域。
     */
    private func drawGameBoard() {
        let width = Int(mGameBoard.frame.width)
        let height = Int(mGameBoard.frame.height)
        
        // 设置线宽
        mDrawer.setLineWidth(0.5)
        
        // 画背景色
        mDrawer.setFillColor(Const.GAME_BOARD_COLOR)
        mDrawer.fill(CGRect(x: 0, y: 0, width: width, height: height))

        // 画线
        mDrawer.setStrokeColor(Const.GAME_BOARD_BORDER_COLOR)
        for i in 1 ... Const.GAME_BOARD_POINT_COUNT_H {
            // 画横线
            mDrawer.move(to: CGPoint(x: Const.GRID_SIZE, y: i * Const.GRID_SIZE))
            mDrawer.addLine(to: CGPoint(x: width - Const.GRID_SIZE, y: i * Const.GRID_SIZE))
            mDrawer.strokePath()

            // 画竖线
            mDrawer.move(to: CGPoint(x: i * Const.GRID_SIZE, y: Const.GRID_SIZE))
            mDrawer.addLine(to: CGPoint(x: i * Const.GRID_SIZE, y: height - Const.GRID_SIZE))
            mDrawer.strokePath()
        }

        // 通知游戏区域重绘
        DispatchQueue.main.async {
            [self] in
            mGameBoard.setNeedsDisplay()
        }
    }
    
    /**
     * 寻找其它用户。
     */
    public func findUsers() {
        if mUsername == "" {
            return
        }

        let request = FindUsersRequest(mUsername)
        self.sendData(request)
    }
    
    /**
     * 游戏结束。
     * @param reason    结束原因
     */
    private func gameOver(_ reason: GameOverReason) {
        // 将状态设置为已结束
        mStatus = .END

        // 发送通知
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: Const.UM_GAME_OVER, object: reason)
        }
    }

    /**
     * 处理寻找其它用户结果。
     * @param request 结果
     */
    private func handleFindUsers(_ result: FindUsersResult) {
        // 发送通知
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: Const.UM_GET_FIND_USERS_RESULT, object: result)
        }
    }

    /**
     * 处理游戏结束。
     * @param gameOver  游戏结束信息
     */
    private func handleGameOver(_ gameOver: GameOver) {
        // 如果没在下棋中，则直接返回
        if mStatus == .END {
            return
        }

        // 将状态设置为已结束
        mStatus = .END

        // 发送通知
        var reason = gameOver.mReason
        if reason == GameOverReason.WON {
            reason = GameOverReason.LOST
        } else if reason == GameOverReason.SELF_ADMIT {
            reason = GameOverReason.RIVAL_ADMIT
        }
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: Const.UM_GAME_OVER, object: reason)
        }
    }
    
    /**
     * 处理挑战请求。
     * @param request 请求
     */
    private func handleInviteRequest(_ request: InviteRequest) {
        // 发送通知
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: Const.UM_GET_INVITE_REQUEST, object: request.mRivalName)
        }
    }

    /**
     * 处理挑战结果。
     * @param result  结果
     */
    private func handleInviteResult(_ result: InviteResult) {
        // 如果接受则游戏开始
        if result.mAccept {
            mRivalName = result.mRivalName
            self.start(true)
        }

        // 发送通知
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: Const.UM_GET_INVITE_RESULT, object: result)
        }
    }

    /**
     * 处理登录结果。
     * @param result    结果
     */
    private func handleLogon(_ result: LogonResult) {
        let succeeded = result.mUsername != ""
        if succeeded {
            // 保存用户名
            mUsername = result.mUsername
        }

        // 发送通知
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: Const.UM_GET_LOGON_RESULT, object: succeeded)
        }
    }

    /**
     * 处理放置棋子。
     * @param putPiece  棋子信息
     */
    private func handlePutPiece(_ putPiece: PutPiece) {
        // 放一个与自己颜色相反的棋子
        _ = self.putPiece(!mIsBlack, x: putPiece.mX, y: putPiece.mY)
        
        // 将状态设置为游戏中
        mStatus = .PLAYING
    }

    /**
     * 处理对手退出游戏事件。
     * @param quitGame  退出游戏信息
     */
    private func handleQuitGame(_ quitGame: QuitGame) {
        // 如果不是当前对手，则直接返回
        if mRivalName != quitGame.mUsername {
            return
        }
        
        // 发送通知
        let isPlaying = mStatus != Status.END
        var info = Dictionary<String, AnyObject>()
        info[Const.KEY_STATUS] = isPlaying as AnyObject
        info[Const.KEY_USERNAME] = quitGame.mUsername as AnyObject
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: Const.UM_RIVAL_QUIT_GAME, object: quitGame.mUsername, userInfo: info)
        }

        // 对手名置空
        mRivalName = ""

        // 将状态设置为已结束
        mStatus = Status.END
    }

    /**
     * 发起挑战。
     * @param rivalName  要挑战的用户的名字
     */
    public func invite(_ rivalName: String) {
        if rivalName.isEmpty {
            return
        }

        // 发送挑战请求
        let request = InviteRequest(mUsername, username: rivalName)
        self.sendData(request)
    }
    
    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private func isWon() -> Bool {
        // 检查横向
        var result = self.checkHorizontal()
        if result {
            return result
        }

        // 检查纵向
        result = self.checkVertical()
        if result {
            return result
        }

        // 检查斜向（从左上到右下）
        result = self.checkOblique1()
        if result {
            return result
        }

        // 检查斜向（从左下到右上）
        result = self.checkOblique2()
        return result
    }

    /**
     * 登录。
     * @param username  用户名
     */
    public func logon(_ username: String) {
        // 发送登录请求
        let request = LogonRequest(username)
        self.sendData(request)
    }

    /**
     * 游戏区域的单击响应方法。
     * @param x, y  用户单击的位置
     */
    public func onGameBoardClicked(_ x: Int, y: Int) {
        let bound = Const.GRID_SIZE / 2  // 格的尺寸的一半

        // 如果不是己方下棋中（对手下棋中或游戏结束）或者 单击的范围超出棋盘，则直接返回
        if mStatus != .PLAYING ||
            x < bound || x > Int(mGameBoard.frame.width) - bound ||
            y < bound || y > Int(mGameBoard.frame.height) - bound {
            return
        }

        // 将用户单击的点转换为离得最近的棋盘点
        var tempX = x
        var tempY = y
        var temp = x % Const.GRID_SIZE
        if temp >= bound {
            tempX += Const.GRID_SIZE - temp
        } else {
            tempX -= temp
        }

        temp = y % Const.GRID_SIZE
        if temp >= bound {
            tempY += Const.GRID_SIZE - temp
        } else {
            tempY -= temp
        }

        // 将窗口坐标转换成棋盘坐标
        tempX = tempX / Const.GRID_SIZE - 1
        tempY = tempY / Const.GRID_SIZE - 1

        // 放置棋子
        let put = self.putPiece(mIsBlack, x: tempX, y: tempY)
        if !put {
            return
        }
        
        // 判断是否胜利
        let won = self.isWon()
        
        // 将已放置的棋子发给对手
        let putPiece = PutPiece(mRivalName, x: tempX, y: tempY)
        self.sendData(putPiece)
        
        // 如果胜利则通知对手，否则等待对手下棋
        if won {
            // 游戏结束
            self.gameOver(.WON)

            // 发送游戏结束数据
            let gameOver = GameOver(.WON, rivalName: mRivalName)
            self.sendData(gameOver)
        } else {
            mStatus = .RIVAL_PLAYING
        }
    }

    /**
     * 处理服务器发来的数据。
     * @param pro   协议
     */
    private func onGetMessageFromServer(_ pro: GomokuProtocol) {
        switch pro.mType {
            case .FIND_USERS:
                self.handleFindUsers(pro as! FindUsersResult)
            case .GAME_OVER:
                self.handleGameOver(pro as! GameOver)
            case .INVITE_REQUEST:
                self.handleInviteRequest(pro as! InviteRequest)
            case .INVITE_RESULT:
                self.handleInviteResult(pro as! InviteResult)
            case .LOGON:
                self.handleLogon(pro as! LogonResult)
            case .PUT_PIECE:
                self.handlePutPiece(pro as! PutPiece)
            case .QUIT_GAME:
                self.handleQuitGame(pro as! QuitGame)
        }
    }
    
    /**
     * 放置棋子。
     * @param isBlack   黑棋则为 true，白棋则为 false
     * @param x, y      棋盘坐标
     * @return 成功则返回 true，否则返回 false
     */
    private func putPiece(_ isBlack: Bool, x: Int, y: Int) -> Bool {
        // 如果超出棋盘区域，则返回 false
        if x >= Const.GAME_BOARD_POINT_COUNT_H || y >= Const.GAME_BOARD_POINT_COUNT_V {
            return false
        }

        // 如果当前点已经有棋子了，则返回 false
        if mPieces[y][x] != .NOTHING {
            return false
        }

        // 画实心圆
        if isBlack {
            mDrawer.setFillColor(UIColor.black.cgColor)
        } else {
            mDrawer.setFillColor(UIColor.white.cgColor)
        }
        
        let rect = CGRect(x: (x + 1) * Const.GRID_SIZE - mPieceRadius, y: (y + 1) * Const.GRID_SIZE - mPieceRadius, width: mPieceRadius * 2, height: mPieceRadius * 2)
        mDrawer.fillEllipse(in: rect)

        // 通知游戏区域重绘
        DispatchQueue.main.async {
            [self] in
            mGameBoard.setNeedsDisplay()
        }

        // 保存最后放置棋子的位置
        mLastX = x
        mLastY = y

        // 保存到所有棋子中
        if isBlack {
            mPieces[y][x] = .BLACK
        } else {
            mPieces[y][x] = .WHITE
        }

        return true
    }

    /**
     * 接收数据。
     */
    @objc private func receiveData() {
        while true {
            // 接收数据长度
            var sizeBuffer = [UInt8](repeating: 0, count: MemoryLayout<Int>.size)
            var read = recv(mSocket, &sizeBuffer, MemoryLayout<Int>.size, 0)
            if read <= 0 {
                break
            }
            var dataSize = 0
            memcpy(&dataSize, &sizeBuffer, MemoryLayout<Int>.size)
            
            // 接收数据
            var proBuffer = [UInt8](repeating: 0, count: dataSize)
            read = recv(mSocket, &proBuffer, dataSize, 0)
            if read <= 0 {
                break
            }
            
            // 将数据转换成协议
            let pro = GomokuProtocol.unArchive(proBuffer, count: dataSize)

            // 处理协议
            self.onGetMessageFromServer(pro)
        }
    }

    /**
     * 游戏重新开始。
     */
    public func restart() {
        // 发送挑战请求
        let request = InviteRequest(mUsername, username: mRivalName)
        self.sendData(request)
    }
 
    /**
     * 发送数据。
     * @param pro   协议
     */
    public func sendData(_ pro: GomokuProtocol) {
        var data = GomokuProtocol.archive(pro)

        // 发送数据长度
        var dataSize = data.count
        send(mSocket, &dataSize, MemoryLayout<Int>.size, 0)
        
        // 发送数据
        send(mSocket, &data, data.count, 0)
        
        // 防止发送过快导致服务器无法响应
        Thread.sleep(forTimeInterval: 0.01)
    }
    
    /**
     * 游戏开始。
     * @param isBlack   黑棋则为 true，白棋则为 false
     */
    public func start(_ isBlack: Bool) {
        // 清空所有棋子
        mPieces = [[Piece]]()
        for _ in 0 ..< Const.GAME_BOARD_POINT_COUNT_V {
            mPieces.append(Array(repeating: .NOTHING, count: Const.GAME_BOARD_POINT_COUNT_H))
        }

        // 重绘游戏区域
        self.drawGameBoard()

        // 设置成员变量
        mStatus = isBlack ? .PLAYING: .RIVAL_PLAYING
        mIsBlack = isBlack
        
        // 发送通知
        DispatchQueue.main.async {
            NotificationCenter.default.post(name: Const.UM_GAME_START, object: self.mRivalName)
        }
    }
}
